/**
 * Copyright © 2023-2030 The ruanrongman Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.rslly.iot.utility.ai.voice.concentus;

/// <summary>
/// Noise shaping quantization state
/// </summary>
class SilkNSQState {

  /// <summary>
  /// Buffer for quantized output signal
  /// </summary>
  final short[] xq = new short[2 * SilkConstants.MAX_FRAME_LENGTH]; // opt: make these arrays
                                                                    // variable-sized since
                                                                    // construction cost is
                                                                    // significant here
  final int[] sLTP_shp_Q14 = new int[2 * SilkConstants.MAX_FRAME_LENGTH];
  final int[] sLPC_Q14 =
      new int[SilkConstants.MAX_SUB_FRAME_LENGTH + SilkConstants.NSQ_LPC_BUF_LENGTH];
  final int[] sAR2_Q14 = new int[SilkConstants.MAX_SHAPE_LPC_ORDER];
  int sLF_AR_shp_Q14 = 0;
  int lagPrev = 0;
  int sLTP_buf_idx = 0;
  int sLTP_shp_buf_idx = 0;
  int rand_seed = 0;
  int prev_gain_Q16 = 0;
  int rewhite_flag = 0;

  void Reset() {
    Arrays.MemSet(xq, (short) 0, 2 * SilkConstants.MAX_FRAME_LENGTH);
    Arrays.MemSet(sLTP_shp_Q14, 0, 2 * SilkConstants.MAX_FRAME_LENGTH);
    Arrays.MemSet(sLPC_Q14, 0,
        SilkConstants.MAX_SUB_FRAME_LENGTH + SilkConstants.NSQ_LPC_BUF_LENGTH);
    Arrays.MemSet(sAR2_Q14, 0, SilkConstants.MAX_SHAPE_LPC_ORDER);
    sLF_AR_shp_Q14 = 0;
    lagPrev = 0;
    sLTP_buf_idx = 0;
    sLTP_shp_buf_idx = 0;
    rand_seed = 0;
    prev_gain_Q16 = 0;
    rewhite_flag = 0;
  }

  // Copies another nsq state to this one
  void Assign(SilkNSQState other) {
    this.sLF_AR_shp_Q14 = other.sLF_AR_shp_Q14;
    this.lagPrev = other.lagPrev;
    this.sLTP_buf_idx = other.sLTP_buf_idx;
    this.sLTP_shp_buf_idx = other.sLTP_shp_buf_idx;
    this.rand_seed = other.rand_seed;
    this.prev_gain_Q16 = other.prev_gain_Q16;
    this.rewhite_flag = other.rewhite_flag;
    System.arraycopy(other.xq, 0, this.xq, 0, 2 * SilkConstants.MAX_FRAME_LENGTH);
    System.arraycopy(other.sLTP_shp_Q14, 0, this.sLTP_shp_Q14, 0,
        2 * SilkConstants.MAX_FRAME_LENGTH);
    System.arraycopy(other.sLPC_Q14, 0, this.sLPC_Q14, 0,
        SilkConstants.MAX_SUB_FRAME_LENGTH + SilkConstants.NSQ_LPC_BUF_LENGTH);
    System.arraycopy(other.sAR2_Q14, 0, this.sAR2_Q14, 0, SilkConstants.MAX_SHAPE_LPC_ORDER);
  }

  private class NSQ_del_dec_struct {

    final int[] sLPC_Q14 =
        new int[SilkConstants.MAX_SUB_FRAME_LENGTH + SilkConstants.NSQ_LPC_BUF_LENGTH];
    final int[] RandState = new int[SilkConstants.DECISION_DELAY];
    final int[] Q_Q10 = new int[SilkConstants.DECISION_DELAY];
    final int[] Xq_Q14 = new int[SilkConstants.DECISION_DELAY];
    final int[] Pred_Q15 = new int[SilkConstants.DECISION_DELAY];
    final int[] Shape_Q14 = new int[SilkConstants.DECISION_DELAY];
    int[] sAR2_Q14;
    int LF_AR_Q14 = 0;
    int Seed = 0;
    int SeedInit = 0;
    int RD_Q10 = 0;

    NSQ_del_dec_struct(int shapingOrder) {
      sAR2_Q14 = new int[shapingOrder];
    }

    void PartialCopyFrom(NSQ_del_dec_struct other, int q14Offset) {
      System.arraycopy(other.sLPC_Q14, q14Offset, sLPC_Q14, q14Offset,
          (SilkConstants.MAX_SUB_FRAME_LENGTH + SilkConstants.NSQ_LPC_BUF_LENGTH - q14Offset));
      System.arraycopy(other.RandState, 0, RandState, 0, SilkConstants.DECISION_DELAY);
      System.arraycopy(other.Q_Q10, 0, Q_Q10, 0, SilkConstants.DECISION_DELAY);
      System.arraycopy(other.Xq_Q14, 0, Xq_Q14, 0, SilkConstants.DECISION_DELAY);
      System.arraycopy(other.Pred_Q15, 0, Pred_Q15, 0, SilkConstants.DECISION_DELAY);
      System.arraycopy(other.Shape_Q14, 0, Shape_Q14, 0, SilkConstants.DECISION_DELAY);
      System.arraycopy(other.sAR2_Q14, 0, sAR2_Q14, 0, sAR2_Q14.length);
      LF_AR_Q14 = other.LF_AR_Q14;
      Seed = other.Seed;
      SeedInit = other.SeedInit;
      RD_Q10 = other.RD_Q10;
    }

    void Assign(NSQ_del_dec_struct other) {
      this.PartialCopyFrom(other, 0);
    }
  }

  private class NSQ_sample_struct {

    int Q_Q10;
    int RD_Q10;
    int xq_Q14;
    int LF_AR_Q14;
    int sLTP_shp_Q14;
    int LPC_exc_Q14;

    void Assign(NSQ_sample_struct other) {
      this.Q_Q10 = other.Q_Q10;
      this.RD_Q10 = other.RD_Q10;
      this.xq_Q14 = other.xq_Q14;
      this.LF_AR_Q14 = other.LF_AR_Q14;
      this.sLTP_shp_Q14 = other.sLTP_shp_Q14;
      this.LPC_exc_Q14 = other.LPC_exc_Q14;
    }
  }

  void silk_NSQ(
      SilkChannelEncoder psEncC, /* I/O Encoder State */
      SideInfoIndices psIndices, /* I/O Quantization Indices */
      int[] x_Q3, /* I Prefiltered input signal */
      byte[] pulses, /* O Quantized pulse signal */
      short[][] PredCoef_Q12, /* I Short term prediction coefs [2][SilkConstants.MAX_LPC_ORDER] */
      short[] LTPCoef_Q14, /*
                            * I Long term prediction coefs [SilkConstants.LTP_ORDER * MAX_NB_SUBFR]
                            */
      short[] AR2_Q13, /*
                        * I Noise shaping coefs [MAX_NB_SUBFR * SilkConstants.MAX_SHAPE_LPC_ORDER]
                        */
      int[] HarmShapeGain_Q14, /* I Long term shaping coefs [MAX_NB_SUBFR] */
      int[] Tilt_Q14, /* I Spectral tilt [MAX_NB_SUBFR] */
      int[] LF_shp_Q14, /* I Low frequency shaping coefs [MAX_NB_SUBFR] */
      int[] Gains_Q16, /* I Quantization step sizes [MAX_NB_SUBFR] */
      int[] pitchL, /* I Pitch lags [MAX_NB_SUBFR] */
      int Lambda_Q10, /* I Rate/distortion tradeoff */
      int LTP_scale_Q14 /* I LTP state scaling */
  ) {
    int k, lag, start_idx, LSF_interpolation_flag;
    int A_Q12, B_Q14, AR_shp_Q13;
    int pxq;
    int[] sLTP_Q15;
    short[] sLTP;
    int HarmShapeFIRPacked_Q14;
    int offset_Q10;
    int[] x_sc_Q10;
    int pulses_ptr = 0;
    int x_Q3_ptr = 0;

    this.rand_seed = psIndices.Seed;

    /* Set unvoiced lag to the previous one, overwrite later for voiced */
    lag = this.lagPrev;

    Inlines.OpusAssert(this.prev_gain_Q16 != 0);

    offset_Q10 =
        SilkTables.silk_Quantization_Offsets_Q10[psIndices.signalType >> 1][psIndices.quantOffsetType];

    if (psIndices.NLSFInterpCoef_Q2 == 4) {
      LSF_interpolation_flag = 0;
    } else {
      LSF_interpolation_flag = 1;
    }

    sLTP_Q15 = new int[psEncC.ltp_mem_length + psEncC.frame_length];
    sLTP = new short[psEncC.ltp_mem_length + psEncC.frame_length];
    x_sc_Q10 = new int[psEncC.subfr_length];
    /* Set up pointers to start of sub frame */
    this.sLTP_shp_buf_idx = psEncC.ltp_mem_length;
    this.sLTP_buf_idx = psEncC.ltp_mem_length;
    pxq = psEncC.ltp_mem_length;
    for (k = 0; k < psEncC.nb_subfr; k++) {
      A_Q12 = (((k >> 1) | (1 - LSF_interpolation_flag)));
      B_Q14 = (k * SilkConstants.LTP_ORDER); // opt: does this indicate a partitioned array?
      AR_shp_Q13 = (k * SilkConstants.MAX_SHAPE_LPC_ORDER); // opt: same here

      /* Noise shape parameters */
      Inlines.OpusAssert(HarmShapeGain_Q14[k] >= 0);
      HarmShapeFIRPacked_Q14 = Inlines.silk_RSHIFT(HarmShapeGain_Q14[k], 2);
      HarmShapeFIRPacked_Q14 |=
          Inlines.silk_LSHIFT((int) Inlines.silk_RSHIFT(HarmShapeGain_Q14[k], 1), 16);

      this.rewhite_flag = 0;
      if (psIndices.signalType == SilkConstants.TYPE_VOICED) {
        /* Voiced */
        lag = pitchL[k];

        /* Re-whitening */
        if ((k & (3 - Inlines.silk_LSHIFT(LSF_interpolation_flag, 1))) == 0) {
          /* Rewhiten with new A coefs */
          start_idx =
              psEncC.ltp_mem_length - lag - psEncC.predictLPCOrder - SilkConstants.LTP_ORDER / 2;
          Inlines.OpusAssert(start_idx > 0);

          Filters.silk_LPC_analysis_filter(sLTP, start_idx, this.xq,
              start_idx + k * psEncC.subfr_length,
              PredCoef_Q12[A_Q12], 0, psEncC.ltp_mem_length - start_idx, psEncC.predictLPCOrder);

          this.rewhite_flag = 1;
          this.sLTP_buf_idx = psEncC.ltp_mem_length;
        }
      }

      silk_nsq_scale_states(psEncC, x_Q3, x_Q3_ptr, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14,
          Gains_Q16, pitchL, psIndices.signalType);

      silk_noise_shape_quantizer(
          psIndices.signalType,
          x_sc_Q10,
          pulses,
          pulses_ptr,
          this.xq,
          pxq,
          sLTP_Q15,
          PredCoef_Q12[A_Q12],
          LTPCoef_Q14,
          B_Q14,
          AR2_Q13,
          AR_shp_Q13,
          lag,
          HarmShapeFIRPacked_Q14,
          Tilt_Q14[k],
          LF_shp_Q14[k],
          Gains_Q16[k],
          Lambda_Q10,
          offset_Q10,
          psEncC.subfr_length,
          psEncC.shapingLPCOrder,
          psEncC.predictLPCOrder);

      x_Q3_ptr += psEncC.subfr_length;
      pulses_ptr += psEncC.subfr_length;
      pxq += psEncC.subfr_length;
    }

    /* Update lagPrev for next frame */
    this.lagPrev = pitchL[psEncC.nb_subfr - 1];

    /* Save quantized speech and noise shaping signals */
    Arrays.MemMove(this.xq, psEncC.frame_length, 0, psEncC.ltp_mem_length);
    Arrays.MemMove(this.sLTP_shp_Q14, psEncC.frame_length, 0, psEncC.ltp_mem_length);
  }

  /**
   * ********************************
   */
  /* silk_noise_shape_quantizer */
  /**
   * ********************************
   */
  private void silk_noise_shape_quantizer(
      int signalType, /* I Signal type */
      int[] x_sc_Q10, /* I [length] */
      byte[] pulses, /* O [length] */
      int pulses_ptr,
      short[] xq, /* O [length] */
      int xq_ptr,
      int[] sLTP_Q15, /* I/O LTP state */
      short[] a_Q12, /* I Short term prediction coefs */
      short[] b_Q14, /* I Long term prediction coefs */
      int b_Q14_ptr,
      short[] AR_shp_Q13, /* I Noise shaping AR coefs */
      int AR_shp_Q13_ptr,
      int lag, /* I Pitch lag */
      int HarmShapeFIRPacked_Q14, /* I */
      int Tilt_Q14, /* I Spectral tilt */
      int LF_shp_Q14, /* I */
      int Gain_Q16, /* I */
      int Lambda_Q10, /* I */
      int offset_Q10, /* I */
      int length, /* I Input length */
      int shapingLPCOrder, /* I Noise shaping AR filter order */
      int predictLPCOrder /* I Prediction filter order */
  ) {
    int i, j;
    int LTP_pred_Q13, LPC_pred_Q10, n_AR_Q12, n_LTP_Q13;
    int n_LF_Q12, r_Q10, rr_Q10, q1_Q0, q1_Q10, q2_Q10, rd1_Q20, rd2_Q20;
    int exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10;
    int tmp1, tmp2, sLF_AR_shp_Q14;
    int psLPC_Q14;
    int shp_lag_ptr;
    int pred_lag_ptr;

    shp_lag_ptr = this.sLTP_shp_buf_idx - lag + SilkConstants.HARM_SHAPE_FIR_TAPS / 2;
    pred_lag_ptr = this.sLTP_buf_idx - lag + SilkConstants.LTP_ORDER / 2;
    Gain_Q10 = Inlines.silk_RSHIFT(Gain_Q16, 6);

    /* Set up short term AR state */
    psLPC_Q14 = SilkConstants.NSQ_LPC_BUF_LENGTH - 1;

    for (i = 0; i < length; i++) {
      /* Generate dither */
      this.rand_seed = Inlines.silk_RAND(this.rand_seed);

      /* Short-term prediction */
      Inlines.OpusAssert(predictLPCOrder == 10 || predictLPCOrder == 16);
      /* Avoids introducing a bias because Inlines.silk_SMLAWB() always rounds to -inf */
      LPC_pred_Q10 = Inlines.silk_RSHIFT(predictLPCOrder, 1);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 0], a_Q12[0]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 1], a_Q12[1]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 2], a_Q12[2]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 3], a_Q12[3]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 4], a_Q12[4]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 5], a_Q12[5]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 6], a_Q12[6]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 7], a_Q12[7]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 8], a_Q12[8]);
      LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 9], a_Q12[9]);
      if (predictLPCOrder == 16) {
        LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 10], a_Q12[10]);
        LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 11], a_Q12[11]);
        LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 12], a_Q12[12]);
        LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 13], a_Q12[13]);
        LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 14], a_Q12[14]);
        LPC_pred_Q10 = Inlines.silk_SMLAWB(LPC_pred_Q10, this.sLPC_Q14[psLPC_Q14 - 15], a_Q12[15]);
      }

      /* Long-term prediction */
      if (signalType == SilkConstants.TYPE_VOICED) {
        /* Unrolled loop */
        /* Avoids introducing a bias because Inlines.silk_SMLAWB() always rounds to -inf */
        LTP_pred_Q13 = 2;
        LTP_pred_Q13 = Inlines.silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr], b_Q14[b_Q14_ptr]);
        LTP_pred_Q13 =
            Inlines.silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr - 1], b_Q14[b_Q14_ptr + 1]);
        LTP_pred_Q13 =
            Inlines.silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr - 2], b_Q14[b_Q14_ptr + 2]);
        LTP_pred_Q13 =
            Inlines.silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr - 3], b_Q14[b_Q14_ptr + 3]);
        LTP_pred_Q13 =
            Inlines.silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr - 4], b_Q14[b_Q14_ptr + 4]);
        pred_lag_ptr += 1;
      } else {
        LTP_pred_Q13 = 0;
      }

      /* Noise shape feedback */
      Inlines.OpusAssert((shapingLPCOrder & 1) == 0);
      /* check that order is even */
      tmp2 = this.sLPC_Q14[psLPC_Q14];
      tmp1 = this.sAR2_Q14[0];
      this.sAR2_Q14[0] = tmp2;
      n_AR_Q12 = Inlines.silk_RSHIFT(shapingLPCOrder, 1);
      n_AR_Q12 = Inlines.silk_SMLAWB(n_AR_Q12, tmp2, AR_shp_Q13[AR_shp_Q13_ptr]);
      for (j = 2; j < shapingLPCOrder; j += 2) {
        tmp2 = this.sAR2_Q14[j - 1];
        this.sAR2_Q14[j - 1] = tmp1;
        n_AR_Q12 = Inlines.silk_SMLAWB(n_AR_Q12, tmp1, AR_shp_Q13[AR_shp_Q13_ptr + j - 1]);
        tmp1 = this.sAR2_Q14[j + 0];
        this.sAR2_Q14[j + 0] = tmp2;
        n_AR_Q12 = Inlines.silk_SMLAWB(n_AR_Q12, tmp2, AR_shp_Q13[AR_shp_Q13_ptr + j]);
      }
      this.sAR2_Q14[shapingLPCOrder - 1] = tmp1;
      n_AR_Q12 =
          Inlines.silk_SMLAWB(n_AR_Q12, tmp1, AR_shp_Q13[AR_shp_Q13_ptr + shapingLPCOrder - 1]);

      n_AR_Q12 = Inlines.silk_LSHIFT32(n_AR_Q12, 1);
      /* Q11 . Q12 */
      n_AR_Q12 = Inlines.silk_SMLAWB(n_AR_Q12, this.sLF_AR_shp_Q14, Tilt_Q14);

      n_LF_Q12 = Inlines.silk_SMULWB(this.sLTP_shp_Q14[this.sLTP_shp_buf_idx - 1], LF_shp_Q14);
      n_LF_Q12 = Inlines.silk_SMLAWT(n_LF_Q12, this.sLF_AR_shp_Q14, LF_shp_Q14);

      Inlines.OpusAssert(lag > 0 || signalType != SilkConstants.TYPE_VOICED);

      /* Combine prediction and noise shaping signals */
      tmp1 = Inlines.silk_SUB32(Inlines.silk_LSHIFT32(LPC_pred_Q10, 2), n_AR_Q12);
      /* Q12 */
      tmp1 = Inlines.silk_SUB32(tmp1, n_LF_Q12);
      /* Q12 */
      if (lag > 0) {
        /* Symmetric, packed FIR coefficients */
        n_LTP_Q13 = Inlines.silk_SMULWB(
            Inlines.silk_ADD32(this.sLTP_shp_Q14[shp_lag_ptr], this.sLTP_shp_Q14[shp_lag_ptr - 2]),
            HarmShapeFIRPacked_Q14);
        n_LTP_Q13 = Inlines.silk_SMLAWT(n_LTP_Q13, this.sLTP_shp_Q14[shp_lag_ptr - 1],
            HarmShapeFIRPacked_Q14);
        n_LTP_Q13 = Inlines.silk_LSHIFT(n_LTP_Q13, 1);
        shp_lag_ptr += 1;

        tmp2 = Inlines.silk_SUB32(LTP_pred_Q13, n_LTP_Q13);
        /* Q13 */
        tmp1 = Inlines.silk_ADD_LSHIFT32(tmp2, tmp1, 1);
        /* Q13 */
        tmp1 = Inlines.silk_RSHIFT_ROUND(tmp1, 3);
        /* Q10 */
      } else {
        tmp1 = Inlines.silk_RSHIFT_ROUND(tmp1, 2);
        /* Q10 */
      }

      r_Q10 = Inlines.silk_SUB32(x_sc_Q10[i], tmp1);
      /* residual error Q10 */

      /* Flip sign depending on dither */
      if (this.rand_seed < 0) {
        r_Q10 = -r_Q10;
      }
      r_Q10 = Inlines.silk_LIMIT_32(r_Q10, -(31 << 10), 30 << 10);

      /* Find two quantization level candidates and measure their rate-distortion */
      q1_Q10 = Inlines.silk_SUB32(r_Q10, offset_Q10);
      q1_Q0 = Inlines.silk_RSHIFT(q1_Q10, 10);
      if (q1_Q0 > 0) {
        q1_Q10 = Inlines.silk_SUB32(Inlines.silk_LSHIFT(q1_Q0, 10),
            SilkConstants.QUANT_LEVEL_ADJUST_Q10);
        q1_Q10 = Inlines.silk_ADD32(q1_Q10, offset_Q10);
        q2_Q10 = Inlines.silk_ADD32(q1_Q10, 1024);
        rd1_Q20 = Inlines.silk_SMULBB(q1_Q10, Lambda_Q10);
        rd2_Q20 = Inlines.silk_SMULBB(q2_Q10, Lambda_Q10);
      } else if (q1_Q0 == 0) {
        q1_Q10 = offset_Q10;
        q2_Q10 = Inlines.silk_ADD32(q1_Q10, 1024 - SilkConstants.QUANT_LEVEL_ADJUST_Q10);
        rd1_Q20 = Inlines.silk_SMULBB(q1_Q10, Lambda_Q10);
        rd2_Q20 = Inlines.silk_SMULBB(q2_Q10, Lambda_Q10);
      } else if (q1_Q0 == -1) {
        q2_Q10 = offset_Q10;
        q1_Q10 = Inlines.silk_SUB32(q2_Q10, 1024 - SilkConstants.QUANT_LEVEL_ADJUST_Q10);
        rd1_Q20 = Inlines.silk_SMULBB(-q1_Q10, Lambda_Q10);
        rd2_Q20 = Inlines.silk_SMULBB(q2_Q10, Lambda_Q10);
      } else {
        /* Q1_Q0 < -1 */
        q1_Q10 = Inlines.silk_ADD32(Inlines.silk_LSHIFT(q1_Q0, 10),
            SilkConstants.QUANT_LEVEL_ADJUST_Q10);
        q1_Q10 = Inlines.silk_ADD32(q1_Q10, offset_Q10);
        q2_Q10 = Inlines.silk_ADD32(q1_Q10, 1024);
        rd1_Q20 = Inlines.silk_SMULBB(-q1_Q10, Lambda_Q10);
        rd2_Q20 = Inlines.silk_SMULBB(-q2_Q10, Lambda_Q10);
      }
      rr_Q10 = Inlines.silk_SUB32(r_Q10, q1_Q10);
      rd1_Q20 = Inlines.silk_SMLABB(rd1_Q20, rr_Q10, rr_Q10);
      rr_Q10 = Inlines.silk_SUB32(r_Q10, q2_Q10);
      rd2_Q20 = Inlines.silk_SMLABB(rd2_Q20, rr_Q10, rr_Q10);

      if (rd2_Q20 < rd1_Q20) {
        q1_Q10 = q2_Q10;
      }

      pulses[pulses_ptr + i] = (byte) Inlines.silk_RSHIFT_ROUND(q1_Q10, 10);

      /* Excitation */
      exc_Q14 = Inlines.silk_LSHIFT(q1_Q10, 4);
      if (this.rand_seed < 0) {
        exc_Q14 = -exc_Q14;
      }

      /* Add predictions */
      LPC_exc_Q14 = Inlines.silk_ADD_LSHIFT32(exc_Q14, LTP_pred_Q13, 1);
      xq_Q14 = Inlines.silk_ADD_LSHIFT32(LPC_exc_Q14, LPC_pred_Q10, 4);

      /* Scale XQ back to normal level before saving */
      xq[xq_ptr + i] = (short) Inlines
          .silk_SAT16(Inlines.silk_RSHIFT_ROUND(Inlines.silk_SMULWW(xq_Q14, Gain_Q10), 8));

      /* Update states */
      psLPC_Q14 += 1;
      this.sLPC_Q14[psLPC_Q14] = xq_Q14;
      sLF_AR_shp_Q14 = Inlines.silk_SUB_LSHIFT32(xq_Q14, n_AR_Q12, 2);
      this.sLF_AR_shp_Q14 = sLF_AR_shp_Q14;

      this.sLTP_shp_Q14[this.sLTP_shp_buf_idx] =
          Inlines.silk_SUB_LSHIFT32(sLF_AR_shp_Q14, n_LF_Q12, 2);
      sLTP_Q15[this.sLTP_buf_idx] = Inlines.silk_LSHIFT(LPC_exc_Q14, 1);
      this.sLTP_shp_buf_idx++;
      this.sLTP_buf_idx++;

      /* Make dither dependent on quantized signal */
      this.rand_seed = Inlines.silk_ADD32_ovflw(this.rand_seed, pulses[pulses_ptr + i]);
    }

    /* Update LPC synth buffer */
    System.arraycopy(this.sLPC_Q14, length, this.sLPC_Q14, 0, SilkConstants.NSQ_LPC_BUF_LENGTH);
  }

  private void silk_nsq_scale_states(
      SilkChannelEncoder psEncC, /* I Encoder State */
      int[] x_Q3, /* I input in Q3 */
      int x_Q3_ptr,
      int[] x_sc_Q10, /* O input scaled with 1/Gain */
      short[] sLTP, /* I re-whitened LTP state in Q0 */
      int[] sLTP_Q15, /* O LTP state matching scaled input */
      int subfr, /* I subframe number */
      int LTP_scale_Q14, /* I */
      int[] Gains_Q16, /* I [MAX_NB_SUBFR] */
      int[] pitchL, /* I Pitch lag [MAX_NB_SUBFR] */
      int signal_type /* I Signal type */
  ) {
    int i, lag;
    int gain_adj_Q16, inv_gain_Q31, inv_gain_Q23;

    lag = pitchL[subfr];
    inv_gain_Q31 = Inlines.silk_INVERSE32_varQ(Inlines.silk_max(Gains_Q16[subfr], 1), 47);
    Inlines.OpusAssert(inv_gain_Q31 != 0);

    /* Calculate gain adjustment factor */
    if (Gains_Q16[subfr] != this.prev_gain_Q16) {
      gain_adj_Q16 = Inlines.silk_DIV32_varQ(this.prev_gain_Q16, Gains_Q16[subfr], 16);
    } else {
      gain_adj_Q16 = (int) 1 << 16;
    }

    /* Scale input */
    inv_gain_Q23 = Inlines.silk_RSHIFT_ROUND(inv_gain_Q31, 8);
    for (i = 0; i < psEncC.subfr_length; i++) {
      x_sc_Q10[i] = Inlines.silk_SMULWW(x_Q3[x_Q3_ptr + i], inv_gain_Q23);
    }

    /* Save inverse gain */
    this.prev_gain_Q16 = Gains_Q16[subfr];

    /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */
    if (this.rewhite_flag != 0) {
      if (subfr == 0) {
        /* Do LTP downscaling */
        inv_gain_Q31 = Inlines.silk_LSHIFT(Inlines.silk_SMULWB(inv_gain_Q31, LTP_scale_Q14), 2);
      }
      for (i = this.sLTP_buf_idx - lag - SilkConstants.LTP_ORDER / 2; i < this.sLTP_buf_idx; i++) {
        Inlines.OpusAssert(i < SilkConstants.MAX_FRAME_LENGTH);
        sLTP_Q15[i] = Inlines.silk_SMULWB(inv_gain_Q31, sLTP[i]);
      }
    }

    /* Adjust for changing gain */
    if (gain_adj_Q16 != (int) 1 << 16) {
      /* Scale long-term shaping state */
      for (i = this.sLTP_shp_buf_idx - psEncC.ltp_mem_length; i < this.sLTP_shp_buf_idx; i++) {
        this.sLTP_shp_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, this.sLTP_shp_Q14[i]);
      }

      /* Scale long-term prediction state */
      if (signal_type == SilkConstants.TYPE_VOICED && this.rewhite_flag == 0) {
        for (i =
            this.sLTP_buf_idx - lag - SilkConstants.LTP_ORDER / 2; i < this.sLTP_buf_idx; i++) {
          sLTP_Q15[i] = Inlines.silk_SMULWW(gain_adj_Q16, sLTP_Q15[i]);
        }
      }

      this.sLF_AR_shp_Q14 = Inlines.silk_SMULWW(gain_adj_Q16, this.sLF_AR_shp_Q14);

      /* Scale short-term prediction and shaping states */
      for (i = 0; i < SilkConstants.NSQ_LPC_BUF_LENGTH; i++) {
        this.sLPC_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, this.sLPC_Q14[i]);
      }
      for (i = 0; i < SilkConstants.MAX_SHAPE_LPC_ORDER; i++) {
        this.sAR2_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, this.sAR2_Q14[i]);
      }
    }
  }

  void silk_NSQ_del_dec(
      SilkChannelEncoder psEncC, /* I Encoder State */
      SideInfoIndices psIndices, /* I/O Quantization Indices */
      int[] x_Q3, /* I Prefiltered input signal */
      byte[] pulses, /* O Quantized pulse signal */
      short[][] PredCoef_Q12, /* I Short term prediction coefs [2 * MAX_LPC_ORDER] */
      short[] LTPCoef_Q14, /* I Long term prediction coefs LTP_ORDER * MAX_NB_SUBFR] */
      short[] AR2_Q13, /* I Noise shaping coefs [MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER] */
      int[] HarmShapeGain_Q14, /* I Long term shaping coefs [MAX_NB_SUBFR] */
      int[] Tilt_Q14, /* I Spectral tilt [MAX_NB_SUBFR] */
      int[] LF_shp_Q14, /* I Low frequency shaping coefs [MAX_NB_SUBFR] */
      int[] Gains_Q16, /* I Quantization step sizes [MAX_NB_SUBFR] */
      int[] pitchL, /* I Pitch lags [MAX_NB_SUBFR] */
      int Lambda_Q10, /* I Rate/distortion tradeoff */
      int LTP_scale_Q14 /* I LTP state scaling */
  ) {
    int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr;
    int last_smple_idx, smpl_buf_idx, decisionDelay;
    int A_Q12;
    int pulses_ptr = 0;
    int pxq;
    int[] sLTP_Q15;
    short[] sLTP;
    int HarmShapeFIRPacked_Q14;
    int offset_Q10;
    int RDmin_Q10, Gain_Q10;
    int[] x_sc_Q10;
    int[] delayedGain_Q10;
    int x_Q3_ptr = 0;
    NSQ_del_dec_struct[] psDelDec;
    NSQ_del_dec_struct psDD;

    /* Set unvoiced lag to the previous one, overwrite later for voiced */
    lag = this.lagPrev;

    Inlines.OpusAssert(this.prev_gain_Q16 != 0);

    /* Initialize delayed decision states */
    psDelDec = new NSQ_del_dec_struct[psEncC.nStatesDelayedDecision];
    for (int c = 0; c < psEncC.nStatesDelayedDecision; c++) {
      psDelDec[c] = new NSQ_del_dec_struct(psEncC.shapingLPCOrder);
    }

    for (k = 0; k < psEncC.nStatesDelayedDecision; k++) {
      psDD = psDelDec[k];
      psDD.Seed = (k + psIndices.Seed) & 3;
      psDD.SeedInit = psDD.Seed;
      psDD.RD_Q10 = 0;
      psDD.LF_AR_Q14 = this.sLF_AR_shp_Q14;
      psDD.Shape_Q14[0] = this.sLTP_shp_Q14[psEncC.ltp_mem_length - 1];
      System.arraycopy(this.sLPC_Q14, 0, psDD.sLPC_Q14, 0, SilkConstants.NSQ_LPC_BUF_LENGTH);
      System.arraycopy(this.sAR2_Q14, 0, psDD.sAR2_Q14, 0, psEncC.shapingLPCOrder);
    }

    offset_Q10 =
        SilkTables.silk_Quantization_Offsets_Q10[psIndices.signalType >> 1][psIndices.quantOffsetType];
    smpl_buf_idx = 0;
    /* index of oldest samples */

    decisionDelay = Inlines.silk_min_int(SilkConstants.DECISION_DELAY, psEncC.subfr_length);

    /* For voiced frames limit the decision delay to lower than the pitch lag */
    if (psIndices.signalType == SilkConstants.TYPE_VOICED) {
      for (k = 0; k < psEncC.nb_subfr; k++) {
        decisionDelay =
            Inlines.silk_min_int(decisionDelay, pitchL[k] - SilkConstants.LTP_ORDER / 2 - 1);
      }
    } else if (lag > 0) {
      decisionDelay = Inlines.silk_min_int(decisionDelay, lag - SilkConstants.LTP_ORDER / 2 - 1);
    }

    if (psIndices.NLSFInterpCoef_Q2 == 4) {
      LSF_interpolation_flag = 0;
    } else {
      LSF_interpolation_flag = 1;
    }

    sLTP_Q15 = new int[psEncC.ltp_mem_length + psEncC.frame_length];
    sLTP = new short[psEncC.ltp_mem_length + psEncC.frame_length];
    x_sc_Q10 = new int[psEncC.subfr_length];
    delayedGain_Q10 = new int[SilkConstants.DECISION_DELAY];

    /* Set up pointers to start of sub frame */
    pxq = psEncC.ltp_mem_length;
    this.sLTP_shp_buf_idx = psEncC.ltp_mem_length;
    this.sLTP_buf_idx = psEncC.ltp_mem_length;
    subfr = 0;
    for (k = 0; k < psEncC.nb_subfr; k++) {
      A_Q12 = (((k >> 1) | (1 - LSF_interpolation_flag)));

      /* Noise shape parameters */
      Inlines.OpusAssert(HarmShapeGain_Q14[k] >= 0);
      HarmShapeFIRPacked_Q14 = Inlines.silk_RSHIFT(HarmShapeGain_Q14[k], 2);
      HarmShapeFIRPacked_Q14 |=
          Inlines.silk_LSHIFT((int) Inlines.silk_RSHIFT(HarmShapeGain_Q14[k], 1), 16);

      this.rewhite_flag = 0;
      if (psIndices.signalType == SilkConstants.TYPE_VOICED) {
        /* Voiced */
        lag = pitchL[k];

        /* Re-whitening */
        if ((k & (3 - Inlines.silk_LSHIFT(LSF_interpolation_flag, 1))) == 0) {
          if (k == 2) {
            /* RESET DELAYED DECISIONS */
            /* Find winner */
            RDmin_Q10 = psDelDec[0].RD_Q10;
            Winner_ind = 0;
            for (i = 1; i < psEncC.nStatesDelayedDecision; i++) {
              if (psDelDec[i].RD_Q10 < RDmin_Q10) {
                RDmin_Q10 = psDelDec[i].RD_Q10;
                Winner_ind = i;
              }
            }
            for (i = 0; i < psEncC.nStatesDelayedDecision; i++) {
              if (i != Winner_ind) {
                psDelDec[i].RD_Q10 += (Integer.MAX_VALUE >> 4);
                Inlines.OpusAssert(psDelDec[i].RD_Q10 >= 0);
              }
            }

            /* Copy final part of signals from winner state to output and long-term filter states */
            psDD = psDelDec[Winner_ind];
            last_smple_idx = smpl_buf_idx + decisionDelay;
            for (i = 0; i < decisionDelay; i++) {
              last_smple_idx = (last_smple_idx - 1) & SilkConstants.DECISION_DELAY_MASK;
              pulses[pulses_ptr + i - decisionDelay] =
                  (byte) Inlines.silk_RSHIFT_ROUND(psDD.Q_Q10[last_smple_idx], 10);
              this.xq[pxq + i - decisionDelay] =
                  (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(
                      Inlines.silk_SMULWW(psDD.Xq_Q14[last_smple_idx], Gains_Q16[1]), 14));
              this.sLTP_shp_Q14[this.sLTP_shp_buf_idx - decisionDelay + i] =
                  psDD.Shape_Q14[last_smple_idx];
            }

            subfr = 0;
          }

          /* Rewhiten with new A coefs */
          start_idx =
              psEncC.ltp_mem_length - lag - psEncC.predictLPCOrder - SilkConstants.LTP_ORDER / 2;
          Inlines.OpusAssert(start_idx > 0);

          Filters.silk_LPC_analysis_filter(sLTP, start_idx, this.xq,
              start_idx + k * psEncC.subfr_length,
              PredCoef_Q12[A_Q12], 0, psEncC.ltp_mem_length - start_idx, psEncC.predictLPCOrder);

          this.sLTP_buf_idx = psEncC.ltp_mem_length;
          this.rewhite_flag = 1;
        }
      }

      silk_nsq_del_dec_scale_states(
          psEncC,
          psDelDec,
          x_Q3,
          x_Q3_ptr,
          x_sc_Q10,
          sLTP,
          sLTP_Q15,
          k,
          psEncC.nStatesDelayedDecision,
          LTP_scale_Q14,
          Gains_Q16,
          pitchL,
          psIndices.signalType,
          decisionDelay);

      BoxedValueInt smpl_buf_idx_boxed = new BoxedValueInt(smpl_buf_idx);
      silk_noise_shape_quantizer_del_dec(
          psDelDec,
          psIndices.signalType,
          x_sc_Q10,
          pulses,
          pulses_ptr,
          this.xq,
          pxq,
          sLTP_Q15,
          delayedGain_Q10,
          PredCoef_Q12[A_Q12],
          LTPCoef_Q14,
          k * SilkConstants.LTP_ORDER,
          AR2_Q13,
          k * SilkConstants.MAX_SHAPE_LPC_ORDER,
          lag,
          HarmShapeFIRPacked_Q14,
          Tilt_Q14[k],
          LF_shp_Q14[k],
          Gains_Q16[k],
          Lambda_Q10,
          offset_Q10,
          psEncC.subfr_length,
          subfr++,
          psEncC.shapingLPCOrder,
          psEncC.predictLPCOrder,
          psEncC.warping_Q16,
          psEncC.nStatesDelayedDecision,
          smpl_buf_idx_boxed,
          decisionDelay);
      smpl_buf_idx = smpl_buf_idx_boxed.Val;

      x_Q3_ptr += psEncC.subfr_length;
      pulses_ptr += psEncC.subfr_length;
      pxq += psEncC.subfr_length;
    }

    /* Find winner */
    RDmin_Q10 = psDelDec[0].RD_Q10;
    Winner_ind = 0;
    for (k = 1; k < psEncC.nStatesDelayedDecision; k++) {
      if (psDelDec[k].RD_Q10 < RDmin_Q10) {
        RDmin_Q10 = psDelDec[k].RD_Q10;
        Winner_ind = k;
      }
    }

    /* Copy final part of signals from winner state to output and long-term filter states */
    psDD = psDelDec[Winner_ind];
    psIndices.Seed = (byte) (psDD.SeedInit);
    last_smple_idx = smpl_buf_idx + decisionDelay;
    Gain_Q10 = Inlines.silk_RSHIFT32(Gains_Q16[psEncC.nb_subfr - 1], 6);
    for (i = 0; i < decisionDelay; i++) {
      last_smple_idx = (last_smple_idx - 1) & SilkConstants.DECISION_DELAY_MASK;
      pulses[pulses_ptr + i - decisionDelay] =
          (byte) Inlines.silk_RSHIFT_ROUND(psDD.Q_Q10[last_smple_idx], 10);
      this.xq[pxq + i - decisionDelay] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(
          Inlines.silk_SMULWW(psDD.Xq_Q14[last_smple_idx], Gain_Q10), 8));
      this.sLTP_shp_Q14[this.sLTP_shp_buf_idx - decisionDelay + i] = psDD.Shape_Q14[last_smple_idx];
    }
    System.arraycopy(psDD.sLPC_Q14, psEncC.subfr_length, this.sLPC_Q14, 0,
        SilkConstants.NSQ_LPC_BUF_LENGTH);
    System.arraycopy(psDD.sAR2_Q14, 0, this.sAR2_Q14, 0, psEncC.shapingLPCOrder);

    /* Update states */
    this.sLF_AR_shp_Q14 = psDD.LF_AR_Q14;
    this.lagPrev = pitchL[psEncC.nb_subfr - 1];

    /* Save quantized speech signal */
    Arrays.MemMove(this.xq, psEncC.frame_length, 0, psEncC.ltp_mem_length);
    Arrays.MemMove(this.sLTP_shp_Q14, psEncC.frame_length, 0, psEncC.ltp_mem_length);
  }

  /**
   * ***************************************
   */
  /* Noise shape quantizer for one subframe */
  /**
   * ***************************************
   */
  private void silk_noise_shape_quantizer_del_dec(
      NSQ_del_dec_struct[] psDelDec, /* I/O Delayed decision states */
      int signalType, /* I Signal type */
      int[] x_Q10, /* I */
      byte[] pulses, /* O */
      int pulses_ptr,
      short[] xq, /* O */
      int xq_ptr,
      int[] sLTP_Q15, /* I/O LTP filter state */
      int[] delayedGain_Q10, /* I/O Gain delay buffer */
      short[] a_Q12, /* I Short term prediction coefs */
      short[] b_Q14, /* I Long term prediction coefs */
      int b_Q14_ptr,
      short[] AR_shp_Q13, /* I Noise shaping coefs */
      int AR_shp_Q13_ptr,
      int lag, /* I Pitch lag */
      int HarmShapeFIRPacked_Q14, /* I */
      int Tilt_Q14, /* I Spectral tilt */
      int LF_shp_Q14, /* I */
      int Gain_Q16, /* I */
      int Lambda_Q10, /* I */
      int offset_Q10, /* I */
      int length, /* I Input length */
      int subfr, /* I Subframe number */
      int shapingLPCOrder, /* I Shaping LPC filter order */
      int predictLPCOrder, /* I Prediction filter order */
      int warping_Q16, /* I */
      int nStatesDelayedDecision, /* I Number of states in decision tree */
      BoxedValueInt smpl_buf_idx, /* I Index to newest samples in buffers */
      int decisionDelay /* I */
  ) {
    int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx;
    int Winner_rand_state;
    int LTP_pred_Q14, LPC_pred_Q14, n_AR_Q14, n_LTP_Q14;
    int n_LF_Q14, r_Q10, rr_Q10, rd1_Q10, rd2_Q10, RDmin_Q10, RDmax_Q10;
    int q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10;
    int tmp1, tmp2, sLF_AR_shp_Q14;
    int pred_lag_ptr, shp_lag_ptr, psLPC_Q14;
    NSQ_sample_struct[] sampleStates;
    NSQ_del_dec_struct psDD;
    int SS_left;
    int SS_right;

    Inlines.OpusAssert(nStatesDelayedDecision > 0);
    sampleStates = new NSQ_sample_struct[2 * nStatesDelayedDecision];
    // [porting note] structs must be initialized manually here
    for (int c = 0; c < 2 * nStatesDelayedDecision; c++) {
      sampleStates[c] = new NSQ_sample_struct();
    }

    shp_lag_ptr = this.sLTP_shp_buf_idx - lag + SilkConstants.HARM_SHAPE_FIR_TAPS / 2;
    pred_lag_ptr = this.sLTP_buf_idx - lag + SilkConstants.LTP_ORDER / 2;
    Gain_Q10 = Inlines.silk_RSHIFT(Gain_Q16, 6);

    for (i = 0; i < length; i++) {
      /* Perform common calculations used in all states */

      /* Long-term prediction */
      if (signalType == SilkConstants.TYPE_VOICED) {
        /* Unrolled loop */
        /* Avoids introducing a bias because Inlines.silk_SMLAWB() always rounds to -inf */
        LTP_pred_Q14 = 2;
        LTP_pred_Q14 =
            Inlines.silk_SMLAWB(LTP_pred_Q14, sLTP_Q15[pred_lag_ptr], b_Q14[b_Q14_ptr + 0]);
        LTP_pred_Q14 =
            Inlines.silk_SMLAWB(LTP_pred_Q14, sLTP_Q15[pred_lag_ptr - 1], b_Q14[b_Q14_ptr + 1]);
        LTP_pred_Q14 =
            Inlines.silk_SMLAWB(LTP_pred_Q14, sLTP_Q15[pred_lag_ptr - 2], b_Q14[b_Q14_ptr + 2]);
        LTP_pred_Q14 =
            Inlines.silk_SMLAWB(LTP_pred_Q14, sLTP_Q15[pred_lag_ptr - 3], b_Q14[b_Q14_ptr + 3]);
        LTP_pred_Q14 =
            Inlines.silk_SMLAWB(LTP_pred_Q14, sLTP_Q15[pred_lag_ptr - 4], b_Q14[b_Q14_ptr + 4]);
        LTP_pred_Q14 = Inlines.silk_LSHIFT(LTP_pred_Q14, 1);
        /* Q13 . Q14 */
        pred_lag_ptr += 1;
      } else {
        LTP_pred_Q14 = 0;
      }

      /* Long-term shaping */
      if (lag > 0) {
        /* Symmetric, packed FIR coefficients */
        n_LTP_Q14 = Inlines.silk_SMULWB(
            Inlines.silk_ADD32(this.sLTP_shp_Q14[shp_lag_ptr], this.sLTP_shp_Q14[shp_lag_ptr - 2]),
            HarmShapeFIRPacked_Q14);
        n_LTP_Q14 = Inlines.silk_SMLAWT(n_LTP_Q14, this.sLTP_shp_Q14[shp_lag_ptr - 1],
            HarmShapeFIRPacked_Q14);
        n_LTP_Q14 = Inlines.silk_SUB_LSHIFT32(LTP_pred_Q14, n_LTP_Q14, 2);
        /* Q12 . Q14 */
        shp_lag_ptr += 1;
      } else {
        n_LTP_Q14 = 0;
      }

      for (k = 0; k < nStatesDelayedDecision; k++) {
        /* Delayed decision state */
        psDD = psDelDec[k];
        int[] psDD_sAR2 = psDD.sAR2_Q14;

        /* Sample state */
        SS_left = 2 * k;
        SS_right = SS_left + 1;

        /* Generate dither */
        psDD.Seed = Inlines.silk_RAND(psDD.Seed);

        /* Pointer used in short term prediction and shaping */
        psLPC_Q14 = SilkConstants.NSQ_LPC_BUF_LENGTH - 1 + i;
        /* Short-term prediction */
        Inlines.OpusAssert(predictLPCOrder == 10 || predictLPCOrder == 16);
        /* Avoids introducing a bias because Inlines.silk_SMLAWB() always rounds to -inf */
        LPC_pred_Q14 = Inlines.silk_RSHIFT(predictLPCOrder, 1);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14], a_Q12[0]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 1], a_Q12[1]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 2], a_Q12[2]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 3], a_Q12[3]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 4], a_Q12[4]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 5], a_Q12[5]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 6], a_Q12[6]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 7], a_Q12[7]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 8], a_Q12[8]);
        LPC_pred_Q14 = Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 9], a_Q12[9]);
        if (predictLPCOrder == 16) {
          LPC_pred_Q14 =
              Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 10], a_Q12[10]);
          LPC_pred_Q14 =
              Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 11], a_Q12[11]);
          LPC_pred_Q14 =
              Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 12], a_Q12[12]);
          LPC_pred_Q14 =
              Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 13], a_Q12[13]);
          LPC_pred_Q14 =
              Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 14], a_Q12[14]);
          LPC_pred_Q14 =
              Inlines.silk_SMLAWB(LPC_pred_Q14, psDD.sLPC_Q14[psLPC_Q14 - 15], a_Q12[15]);
        }
        LPC_pred_Q14 = Inlines.silk_LSHIFT(LPC_pred_Q14, 4);
        /* Q10 . Q14 */


        /* Noise shape feedback */
        Inlines.OpusAssert((shapingLPCOrder & 1) == 0);
        /* check that order is even */
        /* Output of lowpass section */
        tmp2 = Inlines.silk_SMLAWB(psDD.sLPC_Q14[psLPC_Q14], psDD_sAR2[0], warping_Q16);
        /* Output of allpass section */
        tmp1 = Inlines.silk_SMLAWB(psDD_sAR2[0], psDD_sAR2[1] - tmp2, warping_Q16);
        psDD_sAR2[0] = tmp2;
        n_AR_Q14 = Inlines.silk_RSHIFT(shapingLPCOrder, 1);
        n_AR_Q14 = Inlines.silk_SMLAWB(n_AR_Q14, tmp2, AR_shp_Q13[AR_shp_Q13_ptr]);
        /* Loop over allpass sections */
        for (j = 2; j < shapingLPCOrder; j += 2) {
          /* Output of allpass section */
          tmp2 = Inlines.silk_SMLAWB(psDD_sAR2[j - 1], psDD_sAR2[j + 0] - tmp1, warping_Q16);
          psDD_sAR2[j - 1] = tmp1;
          n_AR_Q14 = Inlines.silk_SMLAWB(n_AR_Q14, tmp1, AR_shp_Q13[AR_shp_Q13_ptr + j - 1]);
          /* Output of allpass section */
          tmp1 = Inlines.silk_SMLAWB(psDD_sAR2[j + 0], psDD_sAR2[j + 1] - tmp2, warping_Q16);
          psDD_sAR2[j + 0] = tmp2;
          n_AR_Q14 = Inlines.silk_SMLAWB(n_AR_Q14, tmp2, AR_shp_Q13[AR_shp_Q13_ptr + j]);
        }
        psDD_sAR2[shapingLPCOrder - 1] = tmp1;
        n_AR_Q14 =
            Inlines.silk_SMLAWB(n_AR_Q14, tmp1, AR_shp_Q13[AR_shp_Q13_ptr + shapingLPCOrder - 1]);

        n_AR_Q14 = Inlines.silk_LSHIFT(n_AR_Q14, 1);
        /* Q11 . Q12 */
        n_AR_Q14 = Inlines.silk_SMLAWB(n_AR_Q14, psDD.LF_AR_Q14, Tilt_Q14);
        /* Q12 */
        n_AR_Q14 = Inlines.silk_LSHIFT(n_AR_Q14, 2);
        /* Q12 . Q14 */

        n_LF_Q14 = Inlines.silk_SMULWB(psDD.Shape_Q14[smpl_buf_idx.Val], LF_shp_Q14);
        /* Q12 */
        n_LF_Q14 = Inlines.silk_SMLAWT(n_LF_Q14, psDD.LF_AR_Q14, LF_shp_Q14);
        /* Q12 */
        n_LF_Q14 = Inlines.silk_LSHIFT(n_LF_Q14, 2);
        /* Q12 . Q14 */

        /* Input minus prediction plus noise feedback */
        /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */
        tmp1 = Inlines.silk_ADD32(n_AR_Q14, n_LF_Q14);
        /* Q14 */
        tmp2 = Inlines.silk_ADD32(n_LTP_Q14, LPC_pred_Q14);
        /* Q13 */
        tmp1 = Inlines.silk_SUB32(tmp2, tmp1);
        /* Q13 */
        tmp1 = Inlines.silk_RSHIFT_ROUND(tmp1, 4);
        /* Q10 */

        r_Q10 = Inlines.silk_SUB32(x_Q10[i], tmp1);
        /* residual error Q10 */

        /* Flip sign depending on dither */
        if (psDD.Seed < 0) {
          r_Q10 = -r_Q10;
        }
        r_Q10 = Inlines.silk_LIMIT_32(r_Q10, -(31 << 10), 30 << 10);

        /* Find two quantization level candidates and measure their rate-distortion */
        q1_Q10 = Inlines.silk_SUB32(r_Q10, offset_Q10);
        q1_Q0 = Inlines.silk_RSHIFT(q1_Q10, 10);
        if (q1_Q0 > 0) {
          q1_Q10 = Inlines.silk_SUB32(Inlines.silk_LSHIFT(q1_Q0, 10),
              SilkConstants.QUANT_LEVEL_ADJUST_Q10);
          q1_Q10 = Inlines.silk_ADD32(q1_Q10, offset_Q10);
          q2_Q10 = Inlines.silk_ADD32(q1_Q10, 1024);
          rd1_Q10 = Inlines.silk_SMULBB(q1_Q10, Lambda_Q10);
          rd2_Q10 = Inlines.silk_SMULBB(q2_Q10, Lambda_Q10);
        } else if (q1_Q0 == 0) {
          q1_Q10 = offset_Q10;
          q2_Q10 = Inlines.silk_ADD32(q1_Q10, 1024 - SilkConstants.QUANT_LEVEL_ADJUST_Q10);
          rd1_Q10 = Inlines.silk_SMULBB(q1_Q10, Lambda_Q10);
          rd2_Q10 = Inlines.silk_SMULBB(q2_Q10, Lambda_Q10);
        } else if (q1_Q0 == -1) {
          q2_Q10 = offset_Q10;
          q1_Q10 = Inlines.silk_SUB32(q2_Q10, 1024 - SilkConstants.QUANT_LEVEL_ADJUST_Q10);
          rd1_Q10 = Inlines.silk_SMULBB(-q1_Q10, Lambda_Q10);
          rd2_Q10 = Inlines.silk_SMULBB(q2_Q10, Lambda_Q10);
        } else {
          /* q1_Q0 < -1 */
          q1_Q10 = Inlines.silk_ADD32(Inlines.silk_LSHIFT(q1_Q0, 10),
              SilkConstants.QUANT_LEVEL_ADJUST_Q10);
          q1_Q10 = Inlines.silk_ADD32(q1_Q10, offset_Q10);
          q2_Q10 = Inlines.silk_ADD32(q1_Q10, 1024);
          rd1_Q10 = Inlines.silk_SMULBB(-q1_Q10, Lambda_Q10);
          rd2_Q10 = Inlines.silk_SMULBB(-q2_Q10, Lambda_Q10);
        }
        rr_Q10 = Inlines.silk_SUB32(r_Q10, q1_Q10);
        rd1_Q10 = Inlines.silk_RSHIFT(Inlines.silk_SMLABB(rd1_Q10, rr_Q10, rr_Q10), 10);
        rr_Q10 = Inlines.silk_SUB32(r_Q10, q2_Q10);
        rd2_Q10 = Inlines.silk_RSHIFT(Inlines.silk_SMLABB(rd2_Q10, rr_Q10, rr_Q10), 10);

        if (rd1_Q10 < rd2_Q10) {
          sampleStates[SS_left].RD_Q10 = Inlines.silk_ADD32(psDD.RD_Q10, rd1_Q10);
          sampleStates[SS_right].RD_Q10 = Inlines.silk_ADD32(psDD.RD_Q10, rd2_Q10);
          sampleStates[SS_left].Q_Q10 = q1_Q10;
          sampleStates[SS_right].Q_Q10 = q2_Q10;
        } else {
          sampleStates[SS_left].RD_Q10 = Inlines.silk_ADD32(psDD.RD_Q10, rd2_Q10);
          sampleStates[SS_right].RD_Q10 = Inlines.silk_ADD32(psDD.RD_Q10, rd1_Q10);
          sampleStates[SS_left].Q_Q10 = q2_Q10;
          sampleStates[SS_right].Q_Q10 = q1_Q10;
        }

        /* Update states for best quantization */

        /* Quantized excitation */
        exc_Q14 = Inlines.silk_LSHIFT32(sampleStates[SS_left].Q_Q10, 4);
        if (psDD.Seed < 0) {
          exc_Q14 = -exc_Q14;
        }

        /* Add predictions */
        LPC_exc_Q14 = Inlines.silk_ADD32(exc_Q14, LTP_pred_Q14);
        xq_Q14 = Inlines.silk_ADD32(LPC_exc_Q14, LPC_pred_Q14);

        /* Update states */
        sLF_AR_shp_Q14 = Inlines.silk_SUB32(xq_Q14, n_AR_Q14);
        sampleStates[SS_left].sLTP_shp_Q14 = Inlines.silk_SUB32(sLF_AR_shp_Q14, n_LF_Q14);
        sampleStates[SS_left].LF_AR_Q14 = sLF_AR_shp_Q14;
        sampleStates[SS_left].LPC_exc_Q14 = LPC_exc_Q14;
        sampleStates[SS_left].xq_Q14 = xq_Q14;

        /* Update states for second best quantization */

        /* Quantized excitation */
        exc_Q14 = Inlines.silk_LSHIFT32(sampleStates[SS_right].Q_Q10, 4);
        if (psDD.Seed < 0) {
          exc_Q14 = -exc_Q14;
        }


        /* Add predictions */
        LPC_exc_Q14 = Inlines.silk_ADD32(exc_Q14, LTP_pred_Q14);
        xq_Q14 = Inlines.silk_ADD32(LPC_exc_Q14, LPC_pred_Q14);

        /* Update states */
        sLF_AR_shp_Q14 = Inlines.silk_SUB32(xq_Q14, n_AR_Q14);
        sampleStates[SS_right].sLTP_shp_Q14 = Inlines.silk_SUB32(sLF_AR_shp_Q14, n_LF_Q14);
        sampleStates[SS_right].LF_AR_Q14 = sLF_AR_shp_Q14;
        sampleStates[SS_right].LPC_exc_Q14 = LPC_exc_Q14;
        sampleStates[SS_right].xq_Q14 = xq_Q14;
      }

      smpl_buf_idx.Val = (smpl_buf_idx.Val - 1) & SilkConstants.DECISION_DELAY_MASK;
      /* Index to newest samples */
      last_smple_idx = (smpl_buf_idx.Val + decisionDelay) & SilkConstants.DECISION_DELAY_MASK;
      /* Index to decisionDelay old samples */

      /* Find winner */
      RDmin_Q10 = sampleStates[0].RD_Q10;
      Winner_ind = 0;
      for (k = 1; k < nStatesDelayedDecision; k++) {
        if (sampleStates[k * 2].RD_Q10 < RDmin_Q10) {
          RDmin_Q10 = sampleStates[k * 2].RD_Q10;
          Winner_ind = k;
        }
      }

      /* Increase RD values of expired states */
      Winner_rand_state = psDelDec[Winner_ind].RandState[last_smple_idx];
      for (k = 0; k < nStatesDelayedDecision; k++) {
        if (psDelDec[k].RandState[last_smple_idx] != Winner_rand_state) {
          int k2 = k * 2;
          sampleStates[k2].RD_Q10 =
              Inlines.silk_ADD32(sampleStates[k2].RD_Q10, Integer.MAX_VALUE >> 4);
          sampleStates[k2 + 1].RD_Q10 =
              Inlines.silk_ADD32(sampleStates[k2 + 1].RD_Q10, Integer.MAX_VALUE >> 4);
          Inlines.OpusAssert(sampleStates[k2].RD_Q10 >= 0);
        }
      }

      /* Find worst in first set and best in second set */
      RDmax_Q10 = sampleStates[0].RD_Q10;
      RDmin_Q10 = sampleStates[1].RD_Q10;
      RDmax_ind = 0;
      RDmin_ind = 0;
      for (k = 1; k < nStatesDelayedDecision; k++) {
        int k2 = k * 2;
        /* find worst in first set */
        if (sampleStates[k2].RD_Q10 > RDmax_Q10) {
          RDmax_Q10 = sampleStates[k2].RD_Q10;
          RDmax_ind = k;
        }
        /* find best in second set */
        if (sampleStates[k2 + 1].RD_Q10 < RDmin_Q10) {
          RDmin_Q10 = sampleStates[k2 + 1].RD_Q10;
          RDmin_ind = k;
        }
      }

      /* Replace a state if best from second set outperforms worst in first set */
      if (RDmin_Q10 < RDmax_Q10) {
        psDelDec[RDmax_ind].PartialCopyFrom(psDelDec[RDmin_ind], i);
        sampleStates[RDmax_ind * 2].Assign(sampleStates[RDmin_ind * 2 + 1]);
      }

      /* Write samples from winner to output and long-term filter states */
      psDD = psDelDec[Winner_ind];
      if (subfr > 0 || i >= decisionDelay) {
        pulses[pulses_ptr + i - decisionDelay] =
            (byte) Inlines.silk_RSHIFT_ROUND(psDD.Q_Q10[last_smple_idx], 10);
        xq[xq_ptr + i - decisionDelay] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(
            Inlines.silk_SMULWW(psDD.Xq_Q14[last_smple_idx], delayedGain_Q10[last_smple_idx]), 8));
        this.sLTP_shp_Q14[this.sLTP_shp_buf_idx - decisionDelay] = psDD.Shape_Q14[last_smple_idx];
        sLTP_Q15[this.sLTP_buf_idx - decisionDelay] = psDD.Pred_Q15[last_smple_idx];
      }
      this.sLTP_shp_buf_idx++;
      this.sLTP_buf_idx++;

      /* Update states */
      for (k = 0; k < nStatesDelayedDecision; k++) {
        psDD = psDelDec[k];
        SS_left = k * 2;
        psDD.LF_AR_Q14 = sampleStates[SS_left].LF_AR_Q14;
        psDD.sLPC_Q14[SilkConstants.NSQ_LPC_BUF_LENGTH + i] = sampleStates[SS_left].xq_Q14;
        psDD.Xq_Q14[smpl_buf_idx.Val] = sampleStates[SS_left].xq_Q14;
        psDD.Q_Q10[smpl_buf_idx.Val] = sampleStates[SS_left].Q_Q10;
        psDD.Pred_Q15[smpl_buf_idx.Val] =
            Inlines.silk_LSHIFT32(sampleStates[SS_left].LPC_exc_Q14, 1);
        psDD.Shape_Q14[smpl_buf_idx.Val] = sampleStates[SS_left].sLTP_shp_Q14;
        psDD.Seed = Inlines.silk_ADD32_ovflw(psDD.Seed,
            Inlines.silk_RSHIFT_ROUND(sampleStates[SS_left].Q_Q10, 10));
        psDD.RandState[smpl_buf_idx.Val] = psDD.Seed;
        psDD.RD_Q10 = sampleStates[SS_left].RD_Q10;
      }
      delayedGain_Q10[smpl_buf_idx.Val] = Gain_Q10;
    }

    /* Update LPC states */
    for (k = 0; k < nStatesDelayedDecision; k++) {
      psDD = psDelDec[k];
      System.arraycopy(psDD.sLPC_Q14, length, psDD.sLPC_Q14, 0, SilkConstants.NSQ_LPC_BUF_LENGTH);
    }
  }

  private void silk_nsq_del_dec_scale_states(
      SilkChannelEncoder psEncC, /* I Encoder State */
      NSQ_del_dec_struct[] psDelDec, /* I/O Delayed decision states */
      int[] x_Q3, /* I Input in Q3 */
      int x_Q3_ptr,
      int[] x_sc_Q10, /* O Input scaled with 1/Gain in Q10 */
      short[] sLTP, /* I Re-whitened LTP state in Q0 */
      int[] sLTP_Q15, /* O LTP state matching scaled input */
      int subfr, /* I Subframe number */
      int nStatesDelayedDecision, /* I Number of del dec states */
      int LTP_scale_Q14, /* I LTP state scaling */
      int[] Gains_Q16, /* I [MAX_NB_SUBFR] */
      int[] pitchL, /* I Pitch lag [MAX_NB_SUBFR] */
      int signal_type, /* I Signal type */
      int decisionDelay /* I Decision delay */
  ) {
    int i, k, lag;
    int gain_adj_Q16, inv_gain_Q31, inv_gain_Q23;
    NSQ_del_dec_struct psDD;

    lag = pitchL[subfr];
    inv_gain_Q31 = Inlines.silk_INVERSE32_varQ(Inlines.silk_max(Gains_Q16[subfr], 1), 47);
    Inlines.OpusAssert(inv_gain_Q31 != 0);

    /* Calculate gain adjustment factor */
    if (Gains_Q16[subfr] != this.prev_gain_Q16) {
      gain_adj_Q16 = Inlines.silk_DIV32_varQ(this.prev_gain_Q16, Gains_Q16[subfr], 16);
    } else {
      gain_adj_Q16 = (int) 1 << 16;
    }

    /* Scale input */
    inv_gain_Q23 = Inlines.silk_RSHIFT_ROUND(inv_gain_Q31, 8);
    for (i = 0; i < psEncC.subfr_length; i++) {
      x_sc_Q10[i] = Inlines.silk_SMULWW(x_Q3[x_Q3_ptr + i], inv_gain_Q23);
    }

    /* Save inverse gain */
    this.prev_gain_Q16 = Gains_Q16[subfr];

    /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */
    if (this.rewhite_flag != 0) {
      if (subfr == 0) {
        /* Do LTP downscaling */
        inv_gain_Q31 = Inlines.silk_LSHIFT(Inlines.silk_SMULWB(inv_gain_Q31, LTP_scale_Q14), 2);
      }
      for (i = this.sLTP_buf_idx - lag - SilkConstants.LTP_ORDER / 2; i < this.sLTP_buf_idx; i++) {
        Inlines.OpusAssert(i < SilkConstants.MAX_FRAME_LENGTH);
        sLTP_Q15[i] = Inlines.silk_SMULWB(inv_gain_Q31, sLTP[i]);
      }
    }

    /* Adjust for changing gain */
    if (gain_adj_Q16 != (int) 1 << 16) {
      /* Scale long-term shaping state */
      for (i = this.sLTP_shp_buf_idx - psEncC.ltp_mem_length; i < this.sLTP_shp_buf_idx; i++) {
        this.sLTP_shp_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, this.sLTP_shp_Q14[i]);
      }

      /* Scale long-term prediction state */
      if (signal_type == SilkConstants.TYPE_VOICED && this.rewhite_flag == 0) {
        for (i = this.sLTP_buf_idx - lag - SilkConstants.LTP_ORDER / 2; i < this.sLTP_buf_idx
            - decisionDelay; i++) {
          sLTP_Q15[i] = Inlines.silk_SMULWW(gain_adj_Q16, sLTP_Q15[i]);
        }
      }

      for (k = 0; k < nStatesDelayedDecision; k++) {
        psDD = psDelDec[k];

        /* Scale scalar states */
        psDD.LF_AR_Q14 = Inlines.silk_SMULWW(gain_adj_Q16, psDD.LF_AR_Q14);

        /* Scale short-term prediction and shaping states */
        for (i = 0; i < SilkConstants.NSQ_LPC_BUF_LENGTH; i++) {
          psDD.sLPC_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, psDD.sLPC_Q14[i]);
        }
        for (i = 0; i < psEncC.shapingLPCOrder; i++) {
          psDD.sAR2_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, psDD.sAR2_Q14[i]);
        }
        for (i = 0; i < SilkConstants.DECISION_DELAY; i++) {
          psDD.Pred_Q15[i] = Inlines.silk_SMULWW(gain_adj_Q16, psDD.Pred_Q15[i]);
          psDD.Shape_Q14[i] = Inlines.silk_SMULWW(gain_adj_Q16, psDD.Shape_Q14[i]);
        }
      }
    }
  }
}
